package com.wang.tool.lock.aop;

import com.wang.tool.lock.core.LockBody;
import com.wang.tool.lock.core.LockConfig;
import com.wang.tool.lock.LockConfigCenter;
import com.wang.tool.lock.LockServer;
import com.wang.tool.monitor.LinkLogUtil;
import com.wang.tool.util.CommonUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.AnnotationUtils;

import java.lang.reflect.Method;
import java.util.*;
import java.util.function.Supplier;

/**
 * @Author wangshaopeng
 * @Date 2022/5/10
 */
public class LockAdvice {

    private final LockServer lockServer;

    private final LockKeyBuilder keyBuilder;

    public LockAdvice(LockServer lockServer, LockKeyBuilder keyBuilder) {
        if (lockServer == null || keyBuilder == null) {
            throw new IllegalArgumentException("lockServer and keyBuilder can not be null");
        }
        this.lockServer = lockServer;
        this.keyBuilder = keyBuilder;
    }

    /**
     * 配置表
     */
    private static final Map<String, Meta> METAS = new HashMap<>();


    /**
     * 缓存切面
     *
     * @param point
     * @return
     * @throws Throwable
     */
    public Object lockAround(ProceedingJoinPoint point) throws Throwable {
        //类,方法
        Class<?> targetType = point.getTarget().getClass();
        Method method = ((MethodSignature) point.getSignature()).getMethod();
        Meta meta = getOrInitMeta(targetType, method);
        if (!meta.valid) {
            throw new IllegalArgumentException("lock_annotation_invalid,class:" + targetType.getName() + " method:" + method.getName());
        }

        LockConfig lockConfig = LockConfigCenter.getLockConfig(meta.namespace);
        if (!lockConfig.isOpen()) {
            LinkLogUtil.warn("lock is close,{0}", meta.namespace);
            return point.proceed();
        }

        String lockKey = keyBuilder.build(meta.namespace, meta.keyExpress, point.getArgs());
        if (CommonUtil.isBlank(lockKey)) {
            LinkLogUtil.warn("lock key is blank,{0}", meta.namespace);
            return point.proceed();
        }

        int lockWaitTime = meta.lockWaitTime < 0 ? LockConfigCenter.getDefLockWaitTime() : meta.lockWaitTime;
        int lockSecond = meta.lockSecond < 0 ? LockConfigCenter.getDefLockSecond() : meta.lockSecond;
        return lockServer.lockDo(LockBody.build(meta.namespace, lockKey, lockWaitTime, lockSecond), new Supplier<Object>() {
            @Override
            public Object get() {
                try {
                    return point.proceed();
                } catch (Throwable e) {
                    if (e instanceof RuntimeException) {
                        throw (RuntimeException) e;
                    }
                    if (e instanceof Error) {
                        throw (Error) e;
                    }
                    throw new RuntimeException(e);
                }
            }
        });
    }


    /**
     * 取/刷新配置
     *
     * @param targetType 类 非空
     * @param method     方法 非空
     * @return 非空
     */
    private Meta getOrInitMeta(Class<?> targetType, Method method) {
        //类名#方法名[参数类型]
        String key = targetType.getName().concat("#").concat(method.getName()).concat(Arrays.toString(method.getParameterTypes()));
        return METAS.computeIfAbsent(key, k -> {
            Meta meta = new Meta();
            Lock lock = AnnotationUtils.findAnnotation(method, Lock.class);
            if (lock == null) {
                meta.valid = false;
                return meta;
            }
            String namespace = lock.namespace();
            if (CommonUtil.isBlank(namespace)) {
                meta.valid = false;
                return meta;
            }
            meta.valid = true;
            meta.namespace = namespace;
            meta.keyExpress = lock.keyExpress();
            meta.lockWaitTime = lock.lockWaitTime();
            meta.lockSecond = lock.lockSecond();
            return meta;
        });
    }


    private static class Meta {

        /**
         * 是否是有效的
         */
        private boolean valid;

        private String namespace;

        private String keyExpress;

        private int lockWaitTime;

        private int lockSecond;

    }


}
